package chapter2;

import lombok.extern.slf4j.Slf4j;

import java.security.PrivateKey;

/**
 * @author caiya
 * @date 2020/3/16 21:00
 */
@Slf4j
public class VolatileTest {
    private static boolean flag = false;
    private static int num = 0;

    public static void main(String[] args) throws InterruptedException {
        /**
         * 1、什么是指令重排序？
         *      java内存模型运行编译器和处理器对运行的指令进行重排序以提高程序运行性能，并且只会对不存在数据依赖性的指令进行重排序。
         * 2、什么叫做数据依赖性？
         *      1) int a = 1;
         *      2) int b = 2;
         *      3) int c = a + b;
         *      // 这里的c语句依赖a、b，但是a、b语句没有数据依赖性，所以多线程环境下可能会对1、2操作进行重排序
         * 3、单线程环境下可以保证重排序后的结果和程序顺序执行的结果一致，但是多线程下就不一定了。所以单线程不存在指令重排序的问题。
         * 4、volatile可以确保变量不会被重排：
         *      写volatile变量时，可以确保volatile写之前的代码不会被重排
         *      读volatile变量时，可以确保volatile读之后的代码不会被重排
         *
         * 5、什么是伪共享？
         *      1）现代cpu读取数据一般都是以cache line（缓存行）的为基本单位进行读取，所以通常情况下访问连续存储的数据结构要比随机访问快。这也是为什么数组的访问速度会比
         *      链表结构快的原因，因为数组在内存中都是连续存储的。
         *      2）数组不一定都是连续存储的，有些也是不连续的
         *      3）缓存行的大小通常是64字节，意味着即便cpu只读取1字节的数据，也要一次性拿出64字节
         *      4）缓存失效：如果某个cpu核正在使用的数据所在的缓存行被其他核给修改了，那么这个缓存行就失效了，需要重新读取了。
         *      5）伪共享：如果多个核的线程频繁修改某个缓存行的不同数据，那么就会导致频繁的缓存失效，这种不合理的资源竞争的情况就称为“伪共享”，会严重影响机器的并发效率。
         *
         * 6、如何解决伪共享？
         *      1）cache line大小一般是64字节，那么就可以将成员变量大小填充到64字节
         *      2）使用java8原生的 @Contended 注解（该注解需要设置-XX:-RestrictContended=false才能开启）
         *
         * 7、锁的分类：
         *      1）乐观锁和悲观锁：synchronized属于悲观锁，数据库的version机制类似乐观锁
         *      2）公平锁和非公平锁：
         *          ReentrantLock 提供了公平锁和非公平锁：
         *          ReentrantLock pairLock = new ReentrantLock(true); // 公平锁（先请求锁则先获取到锁资源）
         *          ReentrantLock unPairLock = new ReentrantLock(false);  // 非公平锁（先请求也不一定先获取到锁，取决于线程调度）
         *      3）独占锁和共享锁：
         *          独占锁：任何时候只能保证同时有一个线程拿到锁，例如 ReentrantLock、synchronized，属于悲观锁
         *          共享锁：锁可以被多个线程同时持有，例如 ReadWriteLock 读写锁，属于乐观锁
         *      4）普通方法的 synchronized 锁等同于 this 锁
         *      5）可重入锁：
         *          如果一个线程已经获取了该锁，那么就可以无限次（其实还是有最大次数限制的）进入被锁住的代码或者同一把锁的2个方法块中
         *          synchronized 内置锁属于可重入锁，内部维护一个计数器变量来记录重入次数
         *      6）自旋锁
         *          线程在获取锁时，如果发现锁被其他线程所持有，这时并不会直接阻塞自己，而是在不放弃cpu使用权的情况下进行多次尝试获取锁，默认重试次数为10
         */

        Thread readThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!Thread.currentThread().isInterrupted()) {
                    if (flag) {
                        log.debug(Thread.currentThread().getName() + " num * 2: {}", num * 2);
                    }
                    log.debug(Thread.currentThread().getName() + " is over.");
                }
            }
        });

        Thread writeThread = new Thread(new Runnable() {
            @Override
            public void run() {
                // 这里的2条语句有被重排的风险，解决方法是给flag加volatile，确保flag写之前的语句不会被重排
                num = 2;
                flag = true;
            }
        });

        readThread.start();
        writeThread.start();

        Thread.sleep(50);
        readThread.interrupt();
    }
}
